#include "cgi_action.h"
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <sstream>
#include <vector>
#ifdef WIN32
#include <fcntl.h>
#include <io.h>
#endif
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include "type_def.h"

namespace cgi{

#define DEBUG_TRACE(INFO) do{std::cerr<<__FILE__<<__LINE__<<"trace="<<INFO<<std::endl;}while(0);

template<class T>
void parseEnv(T& store)
{
	static char* sAllEnvName[] = {
		"REQUEST_METHOD",
		"CONTENT_TYPE",
		"CONTENT_LENGTH",
		"QUERY_STRING",
		"SCRIPT_NAME",
		"PATH_INFO",
		"PATH_TRANSLATED",
		"REMOTE_ADDR",
		"REMOTE_HOST",
		"REMOTE_USER",
		"REMOTE_IDENT",
		"AUTH_TYPE",
		"GATEWAY_INTERFACE",
		"SERVER_NAME",
		"SERVER_PORT",
		"SERVER_PROTOCOL",
		"DOCUMENT_ROOT",
		"SERVER_SOFTWARE",
		"HTTP_ACCEPT",
		"HTTP_USER_AGENT",
		"HTTP_REFERER",
		"HTTP_COOKIE"
	};
	for(int i=0; i<sizeof(sAllEnvName)/sizeof(char*); i++)
	{
		std::string sValue;
		char* pEnvValue = getenv(sAllEnvName[i]);
		if(pEnvValue != NULL)
		{
			sValue = pEnvValue;
		}
		store[sAllEnvName[i]] = sValue;
	}
}

template<class T>
void storeKeyValue(T& store, const std::string& sKeyValue)
{
	std::vector<std::string> splits;
	boost::split(splits, sKeyValue, boost::is_any_of("="));
	if(splits.size() == 2)
	{
		boost::trim(splits[0]);
		boost::trim(splits[1]);
		store[splits[0]] = splits[1];
	}
}



template<class T>
void split2KeyValue(T& store, const std::string& sInput, const std::string& sqlit)
{
	std::vector<std::string> splits;
	boost::split(splits, sInput, boost::is_any_of(sqlit));
	for(size_t i = 0 ; i<splits.size(); i++)
	{
		storeKeyValue(store, splits[i]);
	}
}

template<class T>
void splitByLine(T& store, const std::string& sInput, const std::string& sKey)
{
	std::string sTempElement;
	size_t pos = sInput.find(sKey);
	size_t lastPos = 0;
	while(pos != sInput.npos)
	{
		size_t start = sInput.rfind("\r\n", pos);
		size_t end = sInput.find_first_of("\r\n", pos);
		if(start != sInput.npos)
		{
			sTempElement = sInput.substr(lastPos, start - lastPos);
			if(!sTempElement.empty())
			{
				store.push_back(sTempElement);
			}
		}
		lastPos = end;
		pos = sInput.find(sKey, end);
	}
}

bool parse(STFileForm& store, const std::string& sSrc)
{
	store.sFilePath = "test";
	store.sData = sSrc;
	size_t pos = sSrc.find_first_of("Content-Disposition");
	if(pos == sSrc.npos) return false;
	size_t disposEnd = sSrc.find("\r\n", pos);
	if(disposEnd == sSrc.npos) return false;
	std::string sDispos = sSrc.substr(pos, disposEnd - pos);
	std::map<std::string, std::string> storeMap;
	split2KeyValue(storeMap, sDispos, ";");
	std::string sFilePath = storeMap["filename"];
	boost::erase_all(sFilePath, "\"");
	if(sFilePath.empty()) return false;
	store.sFilePath = sFilePath;
	pos = sSrc.find("\r\n\r\n", disposEnd);
	if(pos == sSrc.npos) return false;
	pos += 4;
	store.sData = sSrc.substr(pos, sSrc.size() - pos);
	return true;
}

void setStdMode()
{
#ifdef WIN32
	static bool bSetMod = false;
	if(!bSetMod)
	{
		_setmode(_fileno(stdin),_O_BINARY);
		_setmode(_fileno(stdout),_O_BINARY);
		_setmode(_fileno(stderr),_O_BINARY);
		bSetMod = true;
	}
#endif
}

void getStringFromStdin(std::string& store, size_t size)
{
	DEBUG_TRACE(size);
	char buff[4096];
	size_t readLen;
	size_t havReadLen = 0;
	do
	{
		setStdMode();
		if((size - havReadLen) > sizeof(buff))
		{
			readLen = std::fread(buff, 1, sizeof(buff), stdin);
		}
		else
		{
			readLen = std::fread(buff, 1, size-havReadLen, stdin);
		}
		
		if(readLen > 0)
		{
			store.append(std::string(buff, readLen));
			havReadLen += readLen;
		}else
		{
			if (std::feof(stdin))
			{
				DEBUG_TRACE("read eof");
			}
			else if (std::ferror(stdin))
			{
				DEBUG_TRACE("read bad");
			}
			else
			{
				DEBUG_TRACE("no pipe");
			}
			return ;
		}

	}while(havReadLen > 0 && havReadLen < size);
	 
}

template<class T>
void setDefault(T& store, const std::string& sKey, const std::string& sDefault)
{
	if(store[sKey].empty())
	{
		store[sKey] = sDefault;
	}
}

CgiAction::CgiAction(void)
{
	m_bInit = false;
}


CgiAction::~CgiAction(void)
{
}

int CgiAction::proc()
{
	if(!m_bInit)
	{
		if(init() == 0)
		{
			m_bInit = true;
		}
	}
	parseEnv(ENV);
	split2KeyValue(GET, ENV["QUERY_STRING"], "&");
	if(ENV["REQUEST_METHOD"] == "GET")
	{
		m_bRequestGet = true;
	}
	else
	{
		m_bRequestGet = false;
		size_t contentLen = atoi(ENV["CONTENT_LENGTH"].c_str());
		if(contentLen > 0)
		{
			if(contentLen > POST_MAX_LENGTH)
			{
				return POST_LENGTH_TO_BIG;
			}
			getStringFromStdin(POST_DATA, contentLen);
			split2KeyValue(POST, POST_DATA, "&");
		}
	}
	split2KeyValue(COOKIES, ENV["HTTP_COOKIE"], ";");
	return doAction();
}



bool CgiAction::parseUploadFile(FileFormList& store)
{
	
	std::map<std::string, std::string> storeMap;
	split2KeyValue(storeMap, ENV["CONTENT_TYPE"], " ");
	std::string sBoundary = storeMap["boundary"];
	if(sBoundary.empty())
	{
		return false; //no file
	}
	else
	{
		std::vector<std::string> sFiles;
		splitByLine(sFiles, POST_DATA, sBoundary);
		for(size_t i=0; i<sFiles.size(); i++)
		{
			STFileForm stFile;
			if(parse(stFile, sFiles[i]))
			{
				store.push_back(stFile);
			}
		}
		
	}
	return true;
}

void CgiAction::redirect(const std::string& sUrl)
{
	m_mapResponse["Location"] = sUrl;
	m_mapResponse["Status"] = "302";
	Json::Value result = Json::nullValue;
	result["ret"] = 0;
	result["msg"] = "ok";
	return render(TEXT,result);
}

void CgiAction::setCookie(const std::string& sKey, const std::string& sValue, const std::string& sDomain, int iExpired)
{
	std::string sExists = m_mapResponse["Set-Cookie"];
	if(sExists.empty())
	{
		sExists += sKey + "=" + sValue + ";domain=" + sDomain;
	}
	else
	{
		sExists += " " + sKey + "=" + sValue + ";domain=" + sDomain;
	}
	m_mapResponse["Set-Cookie"] = sExists;
}

void CgiAction::buildResponse(std::string& store, const Json::Value& sjBody)
{
	setStdMode();
	std::string sBody = sjBody.toStyledString();
	static char* sResponseName[] = {
	"Content_Type",
	"Location",
	"Status",
	"Set-Cookie"
	};
	setDefault(m_mapResponse, "Content-Type", "text/plain;charset=utf8");
	setDefault(m_mapResponse, "Status", "200");
	setDefault(m_mapResponse, "Content-Length", boost::lexical_cast<std::string>(sBody.length()));

	std::stringstream ss;
	ss<<"Status:"<<m_mapResponse["Status"]<<"\n";
	if(m_mapResponse["Location"].empty())
	{
		if(!m_mapResponse["Set-Cookie"].empty())
		{
			ss<<"Set-Cookie:"<<m_mapResponse["Set-Cookie"]<<"\n";
		}
		ss<<"Content-Length:"<<m_mapResponse["Content-Length"]<<"\n";
		ss<<"Content-Type:"<<m_mapResponse["Content-Type"]<<"\n\n"; //must last one
		ss<<sBody;
	}
	else
	{
		ss<<"Location:"<<m_mapResponse["Location"]<<"\n\n";
	}
	
	store = ss.str();
}

void CgiAction::render(OutType type, const Json::Value& dataJson)
{
	/*
	Format  Content-Type
	HTML	text/html
	Text	text/plain
	GIF	    image/gif
	JPEG	image/jpeg
	PostScript	application/postscript
	MPEG	video/mpeg
	*/
	switch(type)
	{
	case TEXT:
		{
			m_mapResponse["Content-Type"] = "text/plain";
			break;
		}
	case XML:
		{
			m_mapResponse["Content-Type"] = "text/plain";
			break;
		}
	case JSON:
		{
			m_mapResponse["Content-Type"] = "text/plain";
			break;
		}
	case HTML:
		{
			m_mapResponse["Content-Type"] = "text/html";
			break;
		}
	default:
		{
		}
		return ;
	}
	std::string response;
	buildResponse(response, dataJson);
	size_t ret = std::fwrite(response.c_str(), response.size(), 1, stdout);
	if(ret == 1)
	{
		DEBUG_TRACE(response);		
	}
	std::fflush(stdout);
}

} //end namespace

